百亿级小文件存储,JuiceFS 在自动驾驶行业的最佳实践
自动驾驶是最近几年的热门领域,专注于自动驾驶技术的创业公司、新造车企业、传统车厂都在这个领域投入了大量的资源,推动着 L4、L5 级别自动驾驶体验能尽早进入我们的日常生活。
自动驾驶技术实现的核心环节是自动驾驶模型的训练,训练数据是由汽车实际采集回来的真实道路驾驶视频,数据规模有数 PB 到数十 PB 之多。在模型训练之前,先要对这些原始视频进行处理,截取其中的关键帧保存为照片。然后再由专业数据标注团队在图片上标记关键信息,比如红绿灯、道路标记等。最终经过标记的数十亿图片和标记数据成为真正要「喂给」训练框架的内容。
熟悉分布式系统和分布式存储的朋友一定知道,LOSF(Lots of Small Files,海量小文件)是存储领域的大难题。而在人工智能 CV(Computer Vision)领域中基于 LOSF 的训练又是刚需,包括自动驾驶、人脸识别、物体检测等细分领域。
本篇文章来自 JuiceFS 某自动驾驶行业客户的架构实践,在百亿规模小文件训练场景下进行了一系列成功的探索,希望能为相关行业的应用带来一些参考和启发。
百亿小文件管理的极致挑战
自动驾驶系统的训练数据集大多有数十亿到数百亿小文件(小于 1MiB 的文件),一次训练通常需要数千万到数亿文件。而且训练 worker 每一次生成 mini-batch 都需要频繁访问存储系统,其中大部分是对元数据的请求,因此,元数据性能直接影响模型训练的效率。
这就要求存储系统不仅要具备管理百亿文件的能力,还必须在高并发请求下,保持低时延、高吞吐的元数据性能。
在存储系统选型中,对象存储是能够承载百亿规模文件的,但是缺少原生目录支持、缺少完整 POSIX 语义支持、元数据性能弱这三方面的问题让对象存储并不适合海量小文件训练场景。
在一些常见的分布式文件系统架构设计中,HDFS 并不适合存储小文件,虽然可以采用 Scale-Up NameNode 和联邦(federation)的方式容纳一定规模的数据,但要存储百亿级小文件依然是一件非常困难的事情;CephFS 的 MDS 虽然有 Scale-Out 能力,但单进程的并发处理能力不高,随着 MDS 集群规模的增长进程间协调开销增大,使得整体性能达不到线性增长。
虽然在 TensorFlow 中支持将多个小文件合并成大文件的 TFRecord 格式来降低训练过程中对存储系统的元数据负载压力,但是在自动驾驶领域,这种方案降低了数据集随机取样的精度,而且其它训练框架(如 PyTorch)也不兼容,造成很多不便。
JuiceFS 如何解决?
JuiceFS 是面向云原生环境设计的开源分布式文件系统,JuiceFS 的创新在于:
•可以用任意对象存储作为数据持久层,保存数据内容。无论任何公有云、私有云环境,只要有对象存储服务,都能用 JuiceFS;•100% 兼容 POSIX、HDFS、S3 三大主流访问协议,能对接所有应用;•元数据引擎是可插拔架构,支持包括 Redis、TiKV、MySQL 等多种数据库作为存储引擎,同时,也提供兼具高性能和海量存储的商用元数据引擎。
JuiceFS 的商用元数据引擎采用 Raft 算法组成分布式集群,保证数据的可靠性、一致性和高可用性。元数据全部存储在节点的内存中,保证低时延响应。元数据引擎采用动态目录树方案进行横向扩展,每个分片(shard)是一个独立的 Raft 组,文件系统目录树可以任意划分,分配到需要的分片中,自动均衡与手动均衡相结合。分片机制对于客户端访问透明。
灵活配置缓存大幅提升训练效率
既然训练任务需要频繁访问存储系统,每次经过网络的开销叠加起来也是不小的冗余,目前工业界都在探索存储与计算分离后的缓存加速方案。JuiceFS 已经内置了缓存能力,客户端访问过的数据,可以自动缓存在该节点指定的存储介质上,下次访问就能直接命中缓存,不用再通过网络读取。同样,元数据也会自动缓存到客户端内存中。
缓存机制在使用上是透明的,无需改变现有应用,只要在 JuiceFS 客户端挂载时添加几个参数,说明缓存的路径、容量等信息即可。缓存对于训练加速的效果非常明显,可以参考我们另外一篇文章「如何借助 JuiceFS 为 AI 模型训练提速 7 倍」。缓存不仅能加速训练,还能显著减少对象存储 API 的调用,从而降低费用开销。
对于分布式训练平台来说,相同的训练数据可能会被不同的任务共享,这些任务不一定会被调度到同一个节点上,如果是分布在不同节点那缓存数据还能共享吗?利用 JuiceFS 的「缓存数据共享」特性,多个训练节点共同组成一个缓存集群,在这个集群中的训练任务都可以共享缓存数据。也就是说当训练任务所处的节点没有命中缓存时,能够通过同一集群中的其它节点来获取数据,而不用去请求远端的对象存储。
训练节点可能不是一个静态的资源,特别是在容器平台里,生命周期的变换是很快的,是否会影响缓存数据共享的效果呢?这就要引出下一个问题。
缓存机制在弹性集群中的挑战
每一家自动驾驶领域的公司都有很多算法研究员、工程师,他们的算法要共享公司的计算资源完成训练和验证。从平台角度讲,资源弹性伸缩是一个提高整体利用率的好方法,按需给每个训练任务分配资源,避免浪费。
但在这种弹性伸缩的集群中,前面提到的本地缓存数据会受到一定影响。虽然缓存集群通过一致性哈希确保了当集群成员发生变化时,需要迁移的数据尽量少,但是对于大规模的训练集群来说这样的数据迁移还是会对整体的训练效率造成影响。
有没有一种方法既能满足训练集群资源弹性伸缩的需求,又不显著影响模型训练效率呢?
这就需要 JuiceFS 独有的「独立缓存集群」特性了。
所谓独立缓存集群,就是将负责存储缓存数据的节点独立部署,提供常驻的缓存数据服务。这样不会受训练集群动态变化的影响,让训练任务有更高、更稳定的缓存命中率。
整体系统的架构如下图所示:
比如有一个动态的训练集群 A 和专门用来做缓存的集群 B,他们都需要使用相同的挂载参数 --cache-group=CACHEGROUP
来构建一个缓存组,其中集群 A 的节点挂载时需要加上 --no-sharing
参数。当集群 A 的应用读数据时,如果当前节点的内存和缓存盘上没有该缓存数据,它就会根据一致性哈希从集群 B 中选择一个节点来读取数据。
此时整个系统由 3 级缓存构成:训练节点的系统缓存、训练节点的磁盘缓存、缓存集群 B 中的缓存,用户可以根据具体应用的访问特点配置各个层级的缓存介质和容量。
为了确保当磁盘损坏时不会对训练任务产生影响,JuiceFS 还提供了缓存数据容灾能力。如果缓存节点的磁盘意外损坏,更换新的磁盘后 JuiceFS 可以自动重建需要的缓存数据。
如何实现混合云中的降本增效?
自动驾驶的训练任务需要大量的 GPU 资源,在充分利用的情况下,自己在机房中采购 GPU,可以比使用公有云便宜不少,这也是目前很多自动驾驶公司的选择。但是,在机房中自建存储系统则没这么简单,会遇到两个挑战:
•数据增长快,采购很难跟上扩容速度,一次买太多,又会造成浪费;•维护大规模的存储集群,必须面对磁盘损坏等问题,运维成本高,效率低;
相比自建存储系统,公有云上的对象存储服务可以弹性伸缩,无限扩容,单位成本便宜,数据的可靠性和服务的可用性相比机房自建存储都更高,是存储海量数据的不错选择。
JuiceFS 非常适合这种 IDC 机房 + 公有云的混合云架构。用户将自己的 IDC 机房与公有云专线连接,数据通过 JuiceFS 持久化到公有云对象存储中,在 IDC 机房里设置一个缓存集群,起到缓存数据加速训练的效果,相比每次从对象存储访问数据,既能节省专线带宽,还能节省对象存储 API 调用费用。
当混合云架构结合 JuiceFS 之后,既享受了云存储带来的便利,又通过自建 IDC 降低了 GPU 成本。对于训练平台的使用者、维护者来说都非常简单方便,满足企业多样化的基础设施架构设计需求。
多机房的数据同步与管理
在这个实践案例中,客户有两个 IDC,相距上千公里,训练任务也会被分配到两个 IDC 中,因此数据集也需要在两个 IDC 中被访问。之前,客户是手工维护将数据集复制到两个 IDC 中。使用 JuiceFS 后,「数据镜像」特性可以省去此前的手工劳动,数据实时同步,满足多地协同工作的需求。
具体来说,数据镜像功能需要在两个 IDC 中都部署 JuiceFS 的元数据集群,当数据镜像启用后,原始文件系统会自动将元数据复制到镜像区域。镜像文件系统被挂载后,客户端会从原始文件系统的对象存储拉取数据,写入到镜像文件系统的对象存储。镜像文件系统挂载后,数据会优先从本地的对象存储读取,如果因同步未完成而出现读取失败,则会尝试从原始文件系统的对象存储读取。
启用数据镜像后,所有数据可以自动复制到两个对象存储中,起到异地灾备的作用。如果不需要异地灾备,在镜像区域可以不配置对象存储,只进行元数据的复制,数据可以提前预热到镜像区域的独立缓存集群来加速训练。这样可以省去一份对象存储的成本,本案例中的客户就采用了此方案。
全方位数据安全保护
不管是为了实现辅助驾驶还是真正的自动驾驶,日常都需要通过路采车收集大量的路采数据,这些数据会再经过一些处理流程二次加工以后最终存储到企业的存储系统中。自动驾驶企业对于这些数据的安全性和可靠性有着极高的要求,因此数据保护是一个非常关键的问题。
我们首先来看看企业上云以后的安全问题。很多时候企业对于上云会存在一定的数据安全担忧,特别是当涉及到一些敏感数据时。JuiceFS 提供的「数据加密」特性同时支持传输中加密(encryption in transit)和静态加密(encryption at rest),保障上云过程中各个环节的数据安全性。
其次可能面临的是数据管理问题。为了防止数据泄漏或误操作,企业可能需要针对不同团队、不同用户进行权限管理和控制。JuiceFS 托管服务通过「访问令牌」可以限定某个 IP 范围的读写权限以及可访问的子目录。挂载之后,JuiceFS 支持基于「用户/用户组」 的权限管理模型,可以灵活针对团队或者个人进行权限设置。
如果某个用户已经具备访问某些数据的权限,也还是需要进一步对数据进行保护,比如用户可能误删除或者误更新数据。对于误删除,JuiceFS 托管服务提供的「回收站」功能可以确保数据被删除以后的一段时间内能够再次恢复。
但是如果数据被误更新了或者因为某种原因损坏了,即使有回收站也无济于事,此时 JuiceFS 的「实时数据保护」特性就非常有用了。这个功能的实现原理是保留一定时间的 Raft 日志,这样当数据误更新发生时可以通过回放历史日志的方式将当时的元数据恢复。同时由于 JuiceFS 写入对象存储的文件是分块(block)存储,更新文件不会修改历史的 block 而是生成新的 block,因此只要对象存储上的历史 block 还没有被删除就可以完整恢复数据,就像一个可以随时时光倒流的「时间机器」一样!
总结
完整架构设计
下图是本案例的整体架构图,在机房 A、B 中都部署了 JuiceFS 的元数据集群以及对应的独立缓存集群,模型训练时将会优先通过缓存集群读取数据集,如果缓存没有命中再从对象存储读取数据。在实际测试中,因为缓存命中率非常高,机房 B 几乎不需要跨机房访问对象存储。
下图描述了数据写入流程。客户通过 JuiceFS 提供的 S3 网关写入数据。当新数据写入以后,就会按照前面介绍的数据镜像流程来将元数据复制到另一个机房。同时在两个机房中都有对应的任务负责预热独立缓存集群,确保新数据能够及时建立缓存。
客户收益
这套方案已经上线到客户生产环境中,下面列一些重要指标:
•已经存储了数十亿的文件,仍在持续增长;•JuiceFS 元数据在数十万 QPS 压力下依然能提供 1ms 时延响应;•模型训练吞吐数十 GiB/s;•独立缓存集群命中率 95%+;•两个 IDC 之间数据同步的平均时延在数十毫秒级别。
通过升级到基于 JuiceFS 的存储系统,客户不仅能够轻松管理好海量数据集,同时借助 JuiceFS 的独立缓存集群特性保证了模型训练的效率。运维成本显著降低的同时,机房 + 公有云的混合云架构相比单一公有云的架构 TCO 也更低,既能利用机房高性价比的计算资源,也能结合公有云上弹性的存储资源。
得益于 JuiceFS 完全兼容 POSIX 的特性,客户在迁移过程中,训练任务的代码不需要做任何修改。
通过 JuiceFS 的数据镜像特性,自动地将数据从一个机房同步到另一个机房,解决多地协作问题,也满足了企业异地灾备的需求。
👇 扫码加群 👇